#include "Avalon/IO/BufferedReader.hpp"

using namespace Avalon;
using namespace Avalon::IO;
using namespace Avalon::Threading;
using namespace boost;

BufferedReader::BufferedReader(Carry<Reader> source)
    : m_source(source),
      m_writer(&m_reader),
      m_isBuffering(false) {}

BufferedReader::~BufferedReader() {
  m_writer.Break();
  m_reader.Break();
}

bool BufferedReader::IsAtEndOfDataSource() {
  return false;
}

Async<int>::Ptr BufferedReader::Read(Buffer* destination) {
  lock_guard<mutex> lock(m_mutex);
  EnableBuffering();
  return m_reader.Read(destination);
}

Async<int>::Ptr BufferedReader::Read(void* destination, int size) {
  lock_guard<mutex> lock(m_mutex);
  EnableBuffering();
  return m_reader.Read(destination, size);
}

Async<int>::Ptr BufferedReader::Read(Buffer* destination, int size) {
  lock_guard<mutex> lock(m_mutex);
  EnableBuffering();
  return m_reader.Read(destination, size);
}

void BufferedReader::EnableBuffering() {
  if(m_isBuffering) {
    return;
  }
  BaseAsync::State state = BaseAsync::COMPLETE;
  while(state == BaseAsync::COMPLETE) {
    m_destination.Reset();
    m_destination.Reserve(1024 * 8);
    Async<int>::Ptr result = m_source->Read(&m_destination);
    result->ConnectFinishedSignal(bind(&BufferedReader::OnRead, this, result),
      &state);
    if(state == BaseAsync::COMPLETE) {
      m_writer.Write(m_destination.GetData(), result->Get());
    }
  }
}

void BufferedReader::OnRead(const Async<int>::Ptr& result) {
  lock_guard<mutex> lock(m_mutex);
  assert(result->GetState() == BaseAsync::COMPLETE);
  m_writer.Write(m_destination.GetData(), result->Get());
  BaseAsync::State state = BaseAsync::COMPLETE;
  while(state == BaseAsync::COMPLETE) {
    m_destination.Reset();
    m_destination.Reserve(1024 * 8);
    Async<int>::Ptr result = m_source->Read(&m_destination);
    result->ConnectFinishedSignal(bind(&BufferedReader::OnRead, this, result),
      &state);
    if(state == BaseAsync::COMPLETE) {
      m_writer.Write(m_destination.GetData(), result->Get());
    }
  }
}
